{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Эти заметки я поначалу делал для себя, а потом решил выложить их в качестве туториала - будут рад, если кому-то это покажется полезным или интересным. \n",
    "Все дальнейшие примеры рассмотрены на примере бесплатной СУБД Oracle Database 11g Express Edition, которую можно найти на [oracle.com](http://www.oracle.com/technetwork/database/database-technologies/express-edition/downloads/index.html) и при желании скачать, предварительно зарегистрировавшись. \n",
    "В заключении рассмотрены вопросы быстродействия при загрузке данных из pandas.DataFrame в БД Oracle (проведено сравнение с загрузкой в MS SQL Server, данные и программный код для сравнения взяты из [тюториала Олега (@Oleg)](https://github.com/Yorko/mlcourse_open/blob/master/jupyter_russian/tutorials/Pandas_and_SQL_server_oleg.ipynb) с разрешения автора) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Oracle и macOS"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Прежде всего хотелось бы рассмотреть один из способов установки сервера Oracle в macOS. Пользователи других ОС могут переходить к следующему пункту - для них установка OracleXE не должна вызвать особых сложностей. \n",
    "\n",
    "Для начала понадобится скачать и установить Docker — программное обеспечение для автоматизации развёртывания и управления приложениями в виртуальной среде. Отмечу, что данный вариант не требователен к ресурсам и подойдет для Mac, выпущеных не ранее 2010 года и всё ещё работающих на OS X El Capitan 10.11 или более новых версиях macOs.  \n",
    "\n",
    "Скачать стабильную сборку Docker можно [по ссылке](https://download.docker.com/mac/stable/Docker.dmg). Устанавливаем приложение стандартым способом и запускаем.\n",
    "\n",
    "Далее необходимо использовать официальные Docker images для Oracle Software на github - клонируем git репозиторий командой: \n",
    "\n",
    "**git clone https://github.com/oracle/docker-images.git** \n"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "После чего скачаем [OracleXE для Linux](http://www.oracle.com/technetwork/database/database-technologies/express-edition/downloads/index.html)\n",
    "и скопируем zip-архив OracleXE в каталог ../docker-images/OracleDatabases/dockerfiles/11.2.0.2 клонированного git репозитория:\n",
    "\n",
    "![title](../../img/finder.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Билдим Oracle XE Docker Image запуском скрипта из каталога dockerfiles: \n",
    "\n",
    "**./buildDockerImage.sh -v 11.2.0.2 -x -i** \n",
    "\n",
    "Убедимся, что образ Oracle создан - выполним команду **docker images**:"
   ]
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "\n",
    "![title](../../img/bash.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Запустим новый контейнер:\n",
    "\n",
    "**docker run --name OracleXE --shm-size=1g -p 1521:1521 -p 8080:8080 -e ORACLE_PWD=weblogic1 oracle/database:11.2.0.2-xe** "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "в результате в логах увидим\n",
    "\n",
    "**DATABASE IS READY TO USE!**\n",
    "\n",
    "(для пользователей SYS и SYSTEM установится переданный в ORACLE_PWD пароль weblogic1) "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Увидеть список запущенных контейнеров можно командой: \n",
    "\n",
    "**docker ps**\n",
    "\n",
    "Остановить контейнер:\n",
    "\n",
    "**docker stop OracleXE**\n",
    "\n",
    "Запустить контейнер:\n",
    "\n",
    "**docker start OracleXE**\n",
    "\n",
    "Просмотреть логи:\n",
    "\n",
    "**docker logs OracleXE**"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Уже теперь можно скачать SQLDeveloper или Aqua Data Studio, подключиться к БД (system/weblogic1@localhost/xe) и всё должно работать, но для работы с БД в Jupyter Notebook (запуска sql*plus) необходимо установить Instant Client Package - Basic (+ Instant Client Package - SQL*Plus) [отсюда](http://www.oracle.com/technetwork/topics/intel-macsoft-096467.html)\n",
    "Подробности установки описаны [здесь](https://oracle.github.io/odpi/doc/installation.html#macos)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Подробнее о работе с Docker в macOs можно почитать [на сайте](https://docs.docker.com/docker-for-mac/)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Подключаемся к БД "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Шлюзом между базой данных и языком Python является объект **Connection**. Именно его мы создадим в первую очередь после импорта библиотеки **cx_Oracle**: вызовем метод **connect**, передав в него параметры подключения в виде строки \"пользователь/пароль@хост/SID\". Полученный объект Connection будет в дальнейшем использоваться при создании курсоров для доступа к БД. \n",
    "\n",
    "Все действия будем проводить на демонстрационной БД, которая идет в составе OracleXE, предварительно разблокировав учетную запись пользователя HR - владельца схемы HR.\n",
    "\n",
    "При успешном подключении будет выведен номер версии БД (атрибут **version** объекта Connection)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import cx_Oracle\n",
    "\n",
    "db = cx_Oracle.connect(\"system/weblogic1@localhost/xe\")\n",
    "cursor = db.cursor()\n",
    "cursor.execute(\"ALTER USER HR ACCOUNT UNLOCK IDENTIFIED BY HR\")\n",
    "db.close()\n",
    "db = cx_Oracle.connect(\"HR/HR@localhost/xe\")\n",
    "print(db.version)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Работаем с БД"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Рассмотрим способы получения данных из таблиц БД. Все они основаны на работе с курсорами. Под курсором в Oracle понимается получаемый при выполнении запроса результирующий набор и связанный с ним указатель текущей записи. Простым программам достаточно одного курсора, который можно использовать снова и снова, для более крупных проектов могут потребоваться несколько отдельных курсоров. В библиотеке cx_Oracle курсор создается методом cursor() объекта Connection. \n",
    "\n",
    "Можно выделить 3 этапа обработки операторов SQL, каждому из которых соответствуют методы cx_Oracle:\n",
    "\n",
    "1) Разбор (parsing)\n",
    "\n",
    "2) Выполнение (execute)\n",
    "\n",
    "3) Извлечение (fetch)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Первый этап** не является обязательным, т.к. все операторы автоматически парсятся на этапе выполнения. Метод parsing() можно использовать для предварительной проверки корректности оператора SQL (в случае ошибки возникнет исключение DatabaseError с соответствующим сообщением): "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cursor = db.cursor()\n",
    "cursor.parse(\"select * from departments order by department_id\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Второй этап** - непосредственное выполнение оператора SQL - представлен метод курсора execute(). После выполнения запроса к таблице БД, удобно использовать курсор в качестве итератора в цикле for для последовательного доступа к возвращаемым записям, которые представлены в виде набор кортежей (тюплов или таплов - кому как нравится). Каждому кортежу соответствует одна запись в таблице hr.departments. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cursor.execute(\"select * from departments order by department_id\")\n",
    "for result in cursor:\n",
    "    print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Третий этап** - извлечение данных - не является обязательным, используется только для запросов (поскольку операторы DDL и DCL не возвращают результаты) и представлен несколькими fetch-методами. На курсоре, который не выполнил запрос, fetch-методы вызовут исключение InterfaceError. \n",
    "\n",
    "Метод курсора **fetchone()** возвращает одну текущую запись из результирующего набора данных. При дальнейших вызовах этого метода будут возвращаться следующие записи последовательно. Eсли все данные из курсора были ранее \"извлечены\", метод вернёт None."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cursor.execute(\"select * from departments order by department_id\")\n",
    "row = cursor.fetchone()\n",
    "print(row)\n",
    "row = cursor.fetchone()\n",
    "print(row)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Метод **fetchmany()** возвращает список кортежей - ровно столько записей, сколько было передано в параметре **numRows** (или пустой список, если все записи из курсора были выбраны ранее):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pprint import pprint\n",
    "\n",
    "res = cursor.fetchmany(numRows=3)\n",
    "pprint(res)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Метод **fetchall()** возвращает все оставшиеся записи открытого курсора:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "res = cursor.fetchall()\n",
    "pprint(res)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Обычно используется тот или иной fetch-метод - в зависимости от дальнейших действий над возвращаемыми данными, либо записи последовательно обрабатываются в цикле, когда курсор служит итератором. \n",
    "\n",
    "Если в курсоре используется запрос, то атрибут **description** отображает структуру запрашиваемых данных: выдается список кортежей, где каждый кортеж состоит из имени столбца, типа столбца, видимого размера, внутреннего размера, точности, масштаба и возможно ли null-значение.  "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pprint(cursor.description)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Как известно, в запросах часто используются условия. В качестве подставляемых параметров запроса рекомендуется использовать связываемые переменные. cx_Oracle поддерживает передачу связываемых переменных по имени (query1 и query2) или по позиции (query3). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "named_params = {\"dept_id\": 50, \"sal\": 8000}\n",
    "\n",
    "query1 = cursor.execute(\n",
    "    \"SELECT FIRST_NAME, LAST_NAME FROM hr.employees WHERE department_id = :dept_id AND salary > :sal\",\n",
    "    named_params,\n",
    ")\n",
    "pprint(query1.fetchall())\n",
    "\n",
    "query2 = cursor.execute(\n",
    "    \"SELECT FIRST_NAME, LAST_NAME FROM hr.employees WHERE department_id = :dept_id AND salary > :sal\",\n",
    "    dept_id=50,\n",
    "    sal=8000,\n",
    ")\n",
    "pprint(query2.fetchall())\n",
    "\n",
    "query3 = cursor.execute(\n",
    "    \"SELECT * FROM hr.locations WHERE country_id=:1 AND city=:2\", (\"US\", \"Seattle\")\n",
    ")\n",
    "pprint(query3.fetchall())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "При работе со связываемыми переменными можно сначала подготовить (**prepare()**) запрос, а потом выполнить его. Подготовленные операторы можно выполнять многократно."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cursor.prepare(\"SELECT * FROM jobs WHERE min_salary > : min\")\n",
    "r = cursor.execute(None, {\"min\": 9000})\n",
    "pprint(r.fetchall())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В cx_Oracle существует возможность пакетной обработки операторов SQL при помощи метода **executemany()**, что часто применяется при добавлении данных в таблицы. Вначале необходимо подготовить последовательность (в нашем случае - список), затем передать в качестве параметра методу executemany(). Создадим таблицу, заполним её данными при помощи executemany(), подсчитаем количество строк, а потом удалим таблицу:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "create_table = \"\"\"\n",
    "CREATE TABLE python_modules (\n",
    "module_name VARCHAR2(50) NOT NULL,\n",
    "file_path VARCHAR2(300) NOT NULL\n",
    ")\n",
    "\"\"\"\n",
    "cursor.execute(create_table)\n",
    "\n",
    "from sys import modules\n",
    "\n",
    "M = []\n",
    "for m_name, m_info in modules.items():\n",
    "    try:\n",
    "        M.append((m_name, m_info.__file__))\n",
    "    except AttributeError:\n",
    "        pass\n",
    "\n",
    "cursor.prepare(\"INSERT INTO python_modules(module_name, file_path) VALUES (:1, :2)\")\n",
    "cursor.executemany(None, M)\n",
    "db.commit()\n",
    "r = cursor.execute(\"SELECT COUNT(*) FROM python_modules\")\n",
    "print(cursor.fetchone())\n",
    "\n",
    "cursor.execute(\"DROP TABLE python_modules PURGE\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Работаем с Pandas"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Библиотека Pandas позволяет загружать данные из таблиц Oracle непосредственно в DataFrame, для чего используется метод **read_sql()**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "\n",
    "df_ora = pd.read_sql(\"select * from employees\", con=db)\n",
    "df_ora[:5]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "При этом также можно использовать связываемые переменные в запросах:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_ora = pd.read_sql(\n",
    "    \"select * from hr.employees where EMPLOYEE_ID=:myempno\",\n",
    "    params={\"myempno\": 110},\n",
    "    con=db,\n",
    ")\n",
    "df_ora"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И конечно же всеми любимая визуализация данных: достаточно одного взгляда на график, чтобы оценить уровень зарплаты сотрудников"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import seaborn as sns\n",
    "\n",
    "%matplotlib inline\n",
    "df_ora = pd.read_sql('select LAST_NAME \"Name\", SALARY \"Salary\" from employees', con=db)\n",
    "df_ora.plot(\n",
    "    x=\"Name\",\n",
    "    y=\"Salary\",\n",
    "    title=\"Salary details, from Oracle demo table\",\n",
    "    figsize=(16, 8),\n",
    "    kind=\"bar\",\n",
    "    color=\"blue\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Оценка быстродействия при загрузке данных в БД"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Для загрузки данных в БД можно использовать стандартный метод Pandas.DataFrame **to_sql()**. В параметре con этого метода передается объект **Engine** библиотеки **SQLAlchemy**, поэтому сначала импортируем эту библиотеку, определим параметры подключения к БД, создадим объект **Engine**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sqlalchemy import create_engine\n",
    "\n",
    "#######################################################\n",
    "### DB connection strings config\n",
    "#######################################################\n",
    "tns = \"\"\"\n",
    "  (DESCRIPTION =\n",
    "    (ADDRESS = (PROTOCOL = TCP)(HOST = 0.0.0.0)(PORT = 1521))\n",
    "    (CONNECT_DATA =\n",
    "      (SERVER = DEDICATED)\n",
    "      (SERVICE_NAME = XE)\n",
    "    )\n",
    "  )\n",
    "\"\"\"\n",
    "\n",
    "usr = \"HR\"\n",
    "pwd = \"HR\"\n",
    "\n",
    "engine = create_engine(\"oracle+cx_oracle://%s:%s@%s\" % (usr, pwd, tns))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Чтобы было с чем сравнивать, я решил воспользоваться прекрасным примером из [тюториала Олега (@Oleg)](https://github.com/Yorko/mlcourse_open/blob/master/jupyter_russian/tutorials/Pandas_and_SQL_server_oleg.ipynb). Следующие пять ячеек заимствованы мной у него практически без изменений, за что ему огромное спасибо). Предварительно нужно скачать файлы [excel](https://drive.google.com/open?id=1UlcTpCWSDMVirX8YU-GOuYKkN2dOdXym) в подкаталоги Production и Prices. В результате будут сформированы 2 датафрейма: df_prices с даннами о ценах на электричество (966840 строк) и df_production с данными о плановом производстве электричества (179760 строк)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "path_production = r\"Production\"\n",
    "path_prices = r\"Prices\"\n",
    "list_files_production = os.listdir(path_production)\n",
    "list_files_prices = os.listdir(path_prices)\n",
    "\n",
    "files_production = [f for f in list_files_production if f[-3:] == \"xls\"]\n",
    "files_prices = [f for f in list_files_prices if f[-3:] == \"xls\"]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Функция предобработки файлов с ценами\n",
    "def Prices_preprocessing(path, f):\n",
    "    df_list = []  ## list с DataFrame по каждому часу\n",
    "    for i in range(24):\n",
    "        df = pd.read_excel(path + \"//\" + f, sheet_name=i)  # Считываем файл в DataFrame\n",
    "        df.drop(\n",
    "            [0, 1], inplace=True\n",
    "        )  # Вырезаем первые две строки, не содержащие ничего полезного\n",
    "        df.drop(\n",
    "            df.columns[[1, 2, 3, 5]], axis=1, inplace=True\n",
    "        )  # Удаляем лишние столбцы, оставляем\n",
    "        df.columns = [\n",
    "            \"ID_node\",\n",
    "            \"price\",\n",
    "        ]  # Для красоты переименуем столбцы с ID узлов и ценами\n",
    "\n",
    "        df[\"_datetime\"] = f[:8]  # Вырезаем из названия дату\n",
    "        df[\"_datetime\"] = df[\"_datetime\"].apply(\n",
    "            lambda x: datetime.strptime(x, \"%Y%m%d\").replace(hour=i)\n",
    "        )\n",
    "        df_list.append(df)\n",
    "    df = pd.concat(\n",
    "        df_list, axis=0, ignore_index=True\n",
    "    )  # Соединим данные с разных страниц в один DataFrame\n",
    "    df.fillna(0, inplace=True)  # Заполним \"дырки\" нулями\n",
    "    return df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import time\n",
    "from datetime import datetime\n",
    "\n",
    "# Соединение данных в один DataFrame\n",
    "start = time.time()\n",
    "df_prices = []\n",
    "for f in files_prices:\n",
    "    df_prices.append(Prices_preprocessing(path_prices, f))\n",
    "df_prices = pd.concat(df_prices, axis=0, ignore_index=True)\n",
    "\n",
    "time_prices_preprocessing = time.time() - start\n",
    "print(\"Time of Prices preprocessing = \", round(time_prices_preprocessing, 1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def func(x):\n",
    "    return datetime.strptime(x[0], \"%Y%m%d\").replace(hour=int(x[1]))\n",
    "\n",
    "\n",
    "# Функция предобработки данных по объёму производства\n",
    "def Production_preprocessing(path, f):\n",
    "    df = pd.read_excel(path + \"//\" + f)\n",
    "    df.drop(\n",
    "        df.columns[\n",
    "            [\n",
    "                1,\n",
    "                3,\n",
    "                4,\n",
    "                6,\n",
    "                7,\n",
    "                9,\n",
    "                10,\n",
    "                12,\n",
    "                13,\n",
    "                15,\n",
    "                16,\n",
    "                18,\n",
    "                19,\n",
    "                21,\n",
    "                22,\n",
    "                24,\n",
    "                25,\n",
    "                27,\n",
    "                28,\n",
    "                30,\n",
    "                31,\n",
    "                33,\n",
    "                34,\n",
    "                36,\n",
    "                37,\n",
    "                39,\n",
    "                40,\n",
    "                42,\n",
    "                43,\n",
    "                45,\n",
    "                46,\n",
    "                48,\n",
    "                49,\n",
    "                51,\n",
    "                52,\n",
    "                54,\n",
    "                55,\n",
    "                57,\n",
    "                58,\n",
    "                60,\n",
    "                61,\n",
    "                63,\n",
    "                64,\n",
    "                66,\n",
    "                67,\n",
    "                69,\n",
    "                70,\n",
    "                72,\n",
    "                73,\n",
    "                75,\n",
    "                76,\n",
    "                77,\n",
    "                78,\n",
    "            ]\n",
    "        ],\n",
    "        axis=1,\n",
    "        inplace=True,\n",
    "    )  # Вырезаем ненужные столбцы\n",
    "    df.drop([0, 1, 2, 3, 4, 5], inplace=True)  # Отрезаем верхние лишние строки\n",
    "    df.drop(df.tail(1).index, inplace=True)  # Отрезаем строку \"Итого\"\n",
    "    df.columns = [\"ID_unit\", \"ID_node\"] + [\n",
    "        x for x in range(24)\n",
    "    ]  # Переименуем столбцы, в том числе данным по производству дадим номер соответствующего часа\n",
    "    df[\"_datetime\"] = f[:8]  # Вырезаем из названия дату\n",
    "    df = pd.melt(\n",
    "        df,\n",
    "        id_vars=[\"ID_unit\", \"ID_node\", \"_datetime\"],\n",
    "        value_vars=[x for x in range(24)],\n",
    "    )  # Превратим данные из столбцов по часам в строки (unpivot)\n",
    "    df.rename(columns={\"value\": \"production\", \"variable\": \"hour\"}, inplace=True)\n",
    "    df[\"_datetime\"] = df[[\"_datetime\", \"hour\"]].apply(\n",
    "        func, axis=1\n",
    "    )  # Склеиваем дату и время\n",
    "    df.drop([\"hour\"], axis=1, inplace=True)  # Убираем ненужный уже столбец \"hour\"\n",
    "    return df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "start = time.time()\n",
    "df_production = []\n",
    "for f in files_production:\n",
    "    df_production.append(Production_preprocessing(path_production, f))\n",
    "df_production = pd.concat(df_production, axis=0, ignore_index=True)\n",
    "\n",
    "time_production_preprocessing = time.time() - start\n",
    "print(\"Time of Production preprocessing = \", round(time_production_preprocessing, 1))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ноутбук у меня старенький, поэтому препроцессинг занял больше времени, чем у Олега. Вполне возможно, что и загрузка данных отнимет немало времени, ведь и установка Oracle в виртуальном контейнере Докера, как будто бы не должна способствовать быстродействию..."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В начале используем **первый способ** загрузки - с помощью команды **Insert**:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# cursor.execute('drop table Production purge')\n",
    "\n",
    "create_table = \"\"\"\n",
    "CREATE TABLE Production (\n",
    "ID_unit NUMBER(20) NOT NULL,\n",
    "ID_node NUMBER(20) NOT NULL,\n",
    "datetime DATE NOT NULL,\n",
    "production NUMBER(30) NOT NULL\n",
    ")\n",
    "\"\"\"\n",
    "cursor.execute(create_table)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "start = time.time()\n",
    "\n",
    "cursor.prepare(\"INSERT INTO Production VALUES (:1, :2, :3, :4)\")\n",
    "cursor.executemany(None, df_production.values.tolist())\n",
    "db.commit()\n",
    "\n",
    "time_production_commit = time.time() - start\n",
    "print(time_production_commit)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# cursor.execute('drop table Price purge')\n",
    "\n",
    "create_table = \"\"\"\n",
    "CREATE TABLE Price (\n",
    "ID_node NUMBER(20) NOT NULL,\n",
    "price float NOT NULL,\n",
    "datetime DATE NOT NULL\n",
    ")\n",
    "\"\"\"\n",
    "cursor.execute(create_table)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "start = time.time()\n",
    "\n",
    "cursor.prepare(\"INSERT INTO Price VALUES (:1, :2, :3)\")\n",
    "cursor.executemany(None, df_prices.values.tolist())\n",
    "db.commit()\n",
    "\n",
    "time_price_commit = time.time() - start\n",
    "print(time_price_commit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Результаты немного удивляют - практически в 10 раз быстрее, чем на MS SQL Server. \n",
    "\n",
    "Проверим загрузку **вторым способом** - методом **pandas.DataFrame.to_sql()**. При его использовании не нужно создавать таблицы БД, делать Commit - всё происходит автоматически."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "df_production.to_sql(\"production2\", engine, index=False, if_exists=\"replace\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "df_prices.to_sql(\"prices2\", engine, index=False, if_exists=\"replace\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Вторым способом получили немного худшее быстродействие. Обратим внимание на то, что типы столбцов у создаваемых таблиц определяются автоматически на основании типов столбцов у DataFrame. В нашем примере для столбца production у результирующей таблицы выбрался тип \"CLOB\" (Character Large Object), работа с таким типом отрицательно сказывается на быстродействии, поэтому лучше его заменить на обычный FLOAT."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_production.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Передадим методу to_sql в параметре **dtype** требуемый тип для столбца production и повторим загрузку"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%time\n",
    "df_production.to_sql(\n",
    "    \"production3\",\n",
    "    engine,\n",
    "    index=False,\n",
    "    if_exists=\"replace\",\n",
    "    dtype={\"production\": types.FLOAT},\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Видим, что время загрузки уменьшилось и уже сопоставимо со временем загрузки первого метода (insert)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Графики цен и планового производства"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Из любопытства посмотрим на графики цен и планового производства Саяно-Шушенской ГЭС, где плановое производство электроэнергии в первые дни 2015 года было максимальным"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_prices[df_prices[\"ID_node\"] == 1001068].plot(\n",
    "    x=\"_datetime\", y=\"price\", figsize=(16, 8)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_production[df_production[\"ID_node\"] == 1001068].plot(\n",
    "    x=\"_datetime\", y=\"production\", figsize=(16, 8)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "И на графики ТЭЦ ОАО \"ППГХО\" в Забайкальском крае с самой дешевой ценой в первые дни 2015 года"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_prices[df_prices[\"ID_node\"] == 1001163].plot(\n",
    "    x=\"_datetime\", y=\"price\", figsize=(16, 8)\n",
    ")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_production[df_production[\"ID_node\"] == 1001163].plot(\n",
    "    x=\"_datetime\", y=\"production\", figsize=(16, 8)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Ну и просто цены и плановое производство электричества в масштабах страны, так сказать:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_prices.plot(x=\"_datetime\", y=\"price\", figsize=(16, 8))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df_production.plot(x=\"_datetime\", y=\"production\", figsize=(16, 8))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "В конце работы рекомендуется закрывать открытые курсоры и подключения к БД (а также выключать компьютеры с целью экономии электроэнергии)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "cursor.close()\n",
    "db.close()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "!sudo shutdown"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
